package app.caihan.scframe.repository

import app.caihan.scframe.api.retrofit2.ExceptionHandle
import app.caihan.scframe.api.retrofit2.IBaseResponse
import app.caihan.scframe.api.retrofit2.ResponseThrowable
import app.caihan.scframe.api.retrofit2.ScResult
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.withContext

/**
 * Repository 基类,实现网络请求与本地数据相关业务
 * 单一职责,只做数据相关操作,不涉及到任何UI处理
 * viewModel或者UI拿到数据后根据相关业务去处理UI交互
 * Repository 使用 Flow 对外暴露 IBaseResponse 或者 ScResult 方便viewModel处理交互
 *
 * [ flow<T> {  }.asLiveData()]
 * [ liveData(Dispatchers.IO) {flow<T> {  }.collect {emit(it)}}]
 *
 * @author Seven-C
 * @date 2020/8/17
 */
abstract class BaseRepository {

    /**
     * 只返回缓存或者网络的成功数据,失败抛异常,不建议使用
     *
     * @param remoto 网络数据
     * @param local 本地数据
     * @param save 当网络请求成功后，保存数据等操作
     * @param isUseCache 是否使用缓存
     */
    suspend fun <T> onlyResultCacheNetSuccess(
        onLine: suspend () -> IBaseResponse<T>,
        local: suspend () -> T?,
        save: suspend (T) -> Unit,
        isUseCache: (T?) -> Boolean = { true }
    ): T {
        val localData = local.invoke()
        if (isUseCache(localData)) return localData!!
        else {
            val net = onLine()
            if (net.isSuccess()) {
                return net.data()!!.also { save(it) }
            }
            throw ResponseThrowable(net)
        }
    }

    suspend fun <T : Any> onlyResultCacheNetScResult(
        onLine: suspend () -> IBaseResponse<T>,
        local: suspend () -> T?,
        save: suspend (T) -> Unit,
        isUseCache: (T?) -> Boolean = { true }
    ): ScResult<T> {
        try {
            val localData = local()
            if (isUseCache(localData)) {
                if (localData is T) return ScResult.Success(localData)
                return ScResult.Error(ResponseThrowable(999, "本地数据库异常"))
            } else {
                val net = onLine()
                return if (net.isSuccess()) {
                    //请求成功
                    ScResult.Success(net.data())
                    ScResult.Success(net.data()).also { save(it.data) }
                } else {
                    ScResult.Error(
                        ResponseThrowable(
                            net.code(),
                            net.msg()
                        )
                    )
                }
            }
        } catch (e: Throwable) {
            return ScResult.Error(
                ExceptionHandle.handleException(e)
            )
        }
    }

    /**
     * 只返回成功数据,请求失败抛异常,不建议使用
     */
    suspend fun <T> onlyResultSuccess(
        onLine: suspend () -> IBaseResponse<T>
    ): T {
        val net = onLine()
        if (net.isSuccess()) {
            return net.data()
        }
        throw ResponseThrowable(net)
    }

    suspend fun <T : Any> requestBaseResponse(onLine: suspend () -> IBaseResponse<T>): IBaseResponse<T> {
        return onLine()
    }

    /**
     * 返回带请求成功与失败原因
     */
    suspend fun <T : Any> requestScResult(onLine: suspend () -> IBaseResponse<T>): ScResult<T> =
        withContext(Dispatchers.IO) {
            try {
                val result = onLine()
                if (result.isSuccess()) {
                    //请求成功
                    ScResult.Success(result.data())
                } else {
                    ScResult.Error(
                        ResponseThrowable(
                            result.code(),
                            result.msg()
                        )
                    )
                }
            } catch (e: Throwable) {
                ScResult.Error(
                    ExceptionHandle.handleException(e)
                )
            }
        }


    suspend fun <T : Any> apiCall(call: suspend () -> IBaseResponse<T>): IBaseResponse<T> {
        return call.invoke()
    }

    suspend fun <T : Any> safeApiCall(call: suspend () -> ScResult<T>): ScResult<T> {
        return try {
            call.invoke()
        } catch (e: Exception) {
            // 调用API时抛出的异常
            ScResult.Error(ExceptionHandle.handleException(e))
        }
    }

    suspend fun <T : Any> executeResponse(
        result: IBaseResponse<T>, successBlock: (suspend CoroutineScope.() -> Unit)? = null,
        errorBlock: (suspend CoroutineScope.() -> Unit)? = null
    ): ScResult<T> {
        return coroutineScope {
            if (result.code() == -1) {
                errorBlock?.let { it() }
                ScResult.Error(
                    ResponseThrowable(
                        result.code(),
                        result.msg()
                    )
                )
            } else {
                successBlock?.let { it() }
                ScResult.Success(result.data())
            }
        }
    }


}